﻿using System.CodeDom.Compiler;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using Microsoft.CSharp;
using Microsoft.JScript;
using Microsoft.VisualBasic;
//------- R-C#
using Refl.Meta.Compiling.Wrappers;
using System;

namespace Refl.Dreaming
{
    /// <summary>
    /// DreamWorker class. The goal of this class is to englobe dream common functions, thus making the Dream class as light as possible,
    /// since they will be instantiated many many times in a program.
    /// </summary>
    internal static class DreamWorker
    {
        //bridge from DreamConfig to CompilerParameters for a standard Dream.
        internal static CompilerParameters GenerateCompilerParameters(ref DreamInfo cfg)
        {
            //add compiler parameters and assembly references
            CompilerParameters compilerParams = new CompilerParameters();
            if (cfg.Language != DreamLanguage.JScript) { compilerParams.CompilerOptions = "/optimize"; }
            compilerParams.GenerateExecutable = false;
            compilerParams.GenerateInMemory = false;
            compilerParams.IncludeDebugInformation = cfg.Debug;
            compilerParams.ReferencedAssemblies.AddRange(cfg.AssemblyReferences.ToArray());
            return compilerParams;
        }
        //Compile the code string
        internal static bool Compile(Dream d)
        {
            CompilerResults results;
            switch (d.Info.Language)
            {
                case DreamLanguage.CSharp:
                    { //We must declare a new scope level since in every case the same var names are used.
                        CompilerParameters parms = DreamWorker.GenerateCompilerParameters(ref d.Info);
                        CSharpCodeProvider provider = new CSharpCodeProvider();
                        string enc = Encapsulate.CSharp(d);
                        results = provider.CompileAssemblyFromSource(parms, enc);
                        break;
                    }
                case DreamLanguage.VisualBasic:
                    {
                        CompilerParameters parms = DreamWorker.GenerateCompilerParameters(ref d.Info);
                        VBCodeProvider provider = new VBCodeProvider();
                        string enc = Encapsulate.VisualBasic(d);
                        results = provider.CompileAssemblyFromSource(parms, enc);
                        break;
                    }
                case DreamLanguage.CPlusPlus:
                    {
                        string en = Encapsulate.CPlusPlus(d);
                        string[] res = cl.CompileFromSourceInTemp(en, true, d.Info.Name);
                        results = new CompilerResults(new TempFileCollection(Paths.GetTempPath()));
                        if (File.Exists(res[2]))
                        {
                            results.CompiledAssembly = Assembly.LoadFile(Path.GetFullPath(res[2]));
                        }
                        else
                        {
                            results.Errors.Add(new CompilerError(res[2], 0, 0, "Dream:Invoke.Compile/clError", res[0]));
                            #if DEBUG
                                Console.WriteLine("Host: Dream:Invoke.Compile/clError");
                            #endif
                        }
                        results.Output.AddRange(res);
                        results.PathToAssembly = res[1];
                        break;
                    }
                case DreamLanguage.IL:
                    {
                        string en = Encapsulate.IL(d);
                        string[] res = ilasm.AssembleFromSourceInTemp(en, true, d.Info.Name);
                        results = new CompilerResults(new TempFileCollection(Paths.GetTempPath()));
                        if (File.Exists(res[1]))
                        {
                            results.CompiledAssembly = Assembly.LoadFile(res[1]);
                        }
                        else
                        {
                            results.Errors.Add(new CompilerError(res[2], 0, 0, "Dream:Invoke.Compile/ilasmError", res[0]));
                            #if DEBUG
                                Console.WriteLine("Host: Dream:Invoke.Compile/ilasmError");
                            #endif
                        }
                        results.Output.AddRange(res);
                        results.PathToAssembly = res[1];
                        break;
                    }
                case DreamLanguage.FSharp:
                    {
                        string en = Encapsulate.FSharp(d);
                        string[] res = fsc.CompileFromSourceInTemp(en, true, d.Info.Name);
                        results = new CompilerResults(new TempFileCollection(Paths.GetTempPath()));
                        if (File.Exists(res[2]))
                        {
                            results.CompiledAssembly = Assembly.LoadFile(Path.GetFullPath(res[2]));
                        }
                        else
                        {
                            results.Errors.Add(new CompilerError(res[2], 0, 0, "Dream:Invoke.Compile/fscError", res[0]));
                            #if DEBUG
                                Console.WriteLine("Host: fsc.exe compilation error.");
                            #endif
                        }
                        results.Output.AddRange(res);
                        results.PathToAssembly = res[1];
                        break;
                    }
                case DreamLanguage.IronPython:
                    {
                        throw new System.NotImplementedException("IronPython Dreams are yet to be implemented.");
                    }
                case DreamLanguage.JScript:
                    {
                        CompilerParameters parms = DreamWorker.GenerateCompilerParameters(ref d.Info);
                        JScriptCodeProvider provider = new JScriptCodeProvider();
                        string cd = Encapsulate.JScript(d);
                        results = provider.CompileAssemblyFromSource(parms, cd);
                        break;
                    }
                default: case DreamLanguage.ReflectiveSharp:
                    {
                        CompilerParameters parms = DreamWorker.GenerateCompilerParameters(ref d.Info);
                        CSharpCodeProvider provider = new CSharpCodeProvider();
                        string cd = Encapsulate.ReflectiveCSharp(d);
                        results = provider.CompileAssemblyFromSource(parms, cd);
                        break;
                    }
            }
            d._CResults = results;
            d._isCompiled = true;
            d._HasErrors = (d._CResults.Errors.Count > 0);
            if (d.HasErrors)
            {
                foreach (CompilerError er in d._CResults.Errors)
                {
                    if (d._Errors == null) { d._Errors = new System.Collections.Generic.List<DreamError>(); }
                    d._Errors.Add(new DreamError("Dream:Inkove.Compile/"+er.ErrorNumber, er.ErrorText, er.Line, er.Column, true));
                    d._HasErrors = true;
                }
                #if DEBUG
                    Console.WriteLine("Host: errors: Dream:Invoke.Compile/{...}");
                #endif
                return false;
            }
            return !d._HasErrors;
        }
        //tries to load the type from the compiled assembly.
        internal static bool TryLoadType(Dream d)
        {
            if (!d._isCompiled)
            {
                if (!DreamWorker.Compile(d)) { return false; }
            }
            try
            {
                Assembly DLLAssembly = d._CResults.CompiledAssembly;
                d._FName = DLLAssembly.Location;

                AppDomain.CurrentDomain.Load(DLLAssembly.GetName());
                if (DLLAssembly != null)
                {
                    if(d._Language == Language.JScript)
                        d._Type = DLLAssembly.GetType(string.Format("C_{0}", d.Info.Name));
                    else
                        d._Type = DLLAssembly.GetType(string.Format("N_{0}.C_{0}", d.Info.Name));
                    d._Instance = Activator.CreateInstance(d._Type);
                    foreach(Var v in d._Vars)
                    {
                        #if DEBUG
                            FieldInfo[] f2 = d._Type.GetFields();
                            FieldInfo f = d._Type.GetField(v.Name);
                            f.SetValue(d._Instance, v.Object);
                        #else
                            d._Type.GetField(v.Name).SetValue(d._Instance, v.Object);
                        #endif
                    }
                    d._TypeIsLoaded = true;
                }
                else
                {
                    d._Errors.Add(new DreamError("Dream:Invoke.TryLoadType/NullAssembly","The generated assemble couldn't be found. Check for compiler errors.",null,null,Triple.Unknown));
                    d._HasErrors = true;
                    return false;
                }
                return true;
            }//If something goes wrong while loading the Type
            catch (Exception ex)
            {
                #if DEBUG
                    Console.WriteLine("Error:  An exception occurred while loading the Dream. => ", ex.Message);
                #endif
                d._Errors.Add(new DreamError("Dream:Inkove.TryLoadType/LoadFailure", "Dream loading raised an exception. Message: \"" + ex.Message + "\"", null, null, false));
                d._HasErrors = true;
                return false;
            }
        }
    }
}
